﻿using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine.UIElements;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using EditorPort = UnityEditor.Experimental.GraphView.Port;
namespace NodeEditor
{
    public class NodeView : GraphView
    {
        public new class UxmlFactory : UxmlFactory<NodeView, GraphView.UxmlTraits> { }
        public Tree tree;
        public Action<NodeItem> onSelectNodeItem;
        public NodeView()
        {
            this.Insert(0, new GridBackground());
            this.SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
            this.AddManipulator(new ContentZoomer());
            this.AddManipulator(new ContentDragger());
            this.AddManipulator(new SelectionDragger());
            this.AddManipulator(new RectangleSelector());

            var ussPath = FileUtility.GetFilePath(EditorHelper.uss);
            if (!string.IsNullOrEmpty(ussPath))
            {
                this.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(ussPath));
            }
        }

        public void UpdateTree(Tree tree)
        {
            this.tree = tree;
            this.graphViewChanged -= OnGraphViewChanged;
            DeleteElements(this.graphElements.ToList());
            this.graphViewChanged += OnGraphViewChanged;

            this.Repaint();
        }

        public NodeItem CreateNode(System.Type type)
        {
            var node = ScriptableObject.CreateInstance(type) as AbstractNode;
            node.guid = GUID.Generate().ToString();
            tree.nodes.Add(node);
            var item = AddNode(node);
            AssetDatabase.AddObjectToAsset(node, tree);
            AssetDatabase.SaveAssets();
            return item;
        }

        public NodeItem AddNode(AbstractNode node)
        {
            if (node == null)
                return null;
            NodeItem item = new NodeItem(node);
            item.onSelect = onSelectNodeItem;
            AddElement(item);
            return item;
        }

        public override List<EditorPort> GetCompatiblePorts(EditorPort startPort, NodeAdapter nodeAdapter)
        {
            return ports.ToList().Where(endPort => endPort.direction != startPort.direction
            && endPort.node != startPort.node
            /*&& endPort.portType == startPort.portType*/).ToList();
        }

        private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
        {
            return this.GraphViewChanged(graphViewChange);
        }
    }

    public static class NodeViewRepaint
    {
        public static void Repaint(this NodeView view)
        {
            OnDrawNode(view);
            OnDrawEdge(view);
        }

        static void OnDrawNode(NodeView view)
        {
            view.tree.nodes.ForEach(n => view.AddNode(n));
        }

        static void OnDrawEdge(NodeView view)
        {
            List<string> errorList = new List<string>();
            view.tree.nodes.ForEach(n =>
            {
                if (n == null)
                    return;
                var node = (view.GetElementByGuid(n.guid) as NodeItem).node;

                foreach (var port in node.ports)
                {
                    if (port.direction == PortDirection.Output)
                        continue;

                    var inputPort = view.GetElementByGuid(port.guid) as EditorPort;
                    port.childs.ForEach(id =>
                    {
                        try
                        {
                            var outputPort = view.GetElementByGuid(id) as EditorPort;
                            if (inputPort.direction == outputPort.direction)
                            {
                                errorList.Add(id);
                                return;
                            }
                            var edge = inputPort.ConnectTo(outputPort);
                            view.AddElement(edge);
                        }
                        catch (Exception ex)
                        {
                            errorList.Add(id);
                            Debug.Log(ex);
                        }
                    });

                    foreach (var item in errorList)
                    {
                        port.childs.Remove(item);
                    }
                    if(errorList.Count>0)
                    {
                        EditorUtility.SetDirty(node);
                    }
                    errorList.Clear();
                }
            });

        }
    }
}
